// Copyright (c) 2022 Presto Labs Pte. Ltd.
// Author: junglekim

#include "coin2/strategy/vmm/vmm.h"

#include <limits>

#include "coin2/strategy/vmm/vmm_factory.h"

namespace coin2::strategy::vmm {

VmmStrategy::VmmStrategy(
    const std::string& strat_json,
    const std::optional<int64_t>& ts,
    const bool& verbose)
    : start_time_(ts.has_value() ? *ts : GetCurrentTimestamp()), verbose_{verbose} {
  std::ifstream in(strat_json);
  config_ = ::nlohmann::json::parse(in);
  SPDLOG_INFO(config_.dump(2));
  for (auto& exe_config : config_["executors"].items()) {
    auto& symbol = exe_config.key();
    PassiveExecutorConfig config;
    if (exe_config.value().contains("max_posting_period_sec") &&
        exe_config.value().contains("order_update_period_sec")) {
      exe_config.value()["max_posting_period"] =
          exe_config.value()["max_posting_period_sec"].get<double>() * 1e9;
      exe_config.value()["order_update_period"] =
          exe_config.value()["order_update_period_sec"].get<double>() * 1e9;
    }
    CHECK(JsonStringToMessage(exe_config.value().dump(), &config).ok());
    exe_configs_[symbol] = config;
    auto& symbol_conf = config_["oc_config"]["configs"][symbol];
    symbol_conf["min_pos"] = config.min_pos();
    symbol_conf["max_pos"] = config.max_pos();
    symbol_conf["lot_size"] = config.lot_size();
    symbol_conf["reserve"] = config.reserve();

    if (symbol_conf.contains("contracts")) {
      for (const auto& [k, v] : symbol_conf["contracts"].items()) {
        if (k == "owner") {
          opt_owners_[symbol] = v;
        } else {
          opt_configs_[symbol][k] = v;
        }
      }
    }
  }

  dry_report_ = config_["dry_report"];
  dry_run_ = config_["dry_run"];
  avoid_arbi_ = config_["avoid_arbi"];
  timer_freeze_ms_ =
      config_.contains("timer_freeze_ms") ? config_["timer_freeze_ms"].get<int>() : 0;
  for (auto& latency : config_["pricer_config"]["latency_ms"].items()) {
    std::string exch = latency.key();
    latency_[exch] = latency.value();
  }
  pricer_ = CreateNewVmmPricer(config_["pricer_config"], avoid_arbi_, verbose_);
  controller_ = CreateNewVmmOrderController(config_["oc_config"], dry_run_, start_time_, verbose_);
  if (config_.contains("arbi_config"))
    arbitrageur_ = CreateNewVmmArbitrageur(config_["arbi_config"], dry_run_, verbose_);
}

void VmmStrategy::Init(Driver* driver) {
  driver_ = driver;
  for (auto& [symbol, exe_config] : exe_configs_) {
    std::unique_ptr<IProduct> product = CreateProductFromUniqueString(symbol, start_time_);
    const auto& symbol_conf = config_["oc_config"]["configs"][symbol];
    std::optional<double> lot_size_quote = symbol_conf.contains("lot_size_quote")
                                               ? std::optional(symbol_conf["lot_size_quote"])
                                               : std::nullopt;
    std::optional<double> qty_cancel_pct = symbol_conf.contains("qty_cancel_pct")
                                               ? std::optional(symbol_conf["qty_cancel_pct"])
                                               : std::nullopt;
    const bool is_randomised_post = symbol_conf.contains("is_randomised_post")
                                        ? symbol_conf["is_randomised_post"].get<bool>()
                                        : false;
    const int cancel2order_ratio = symbol_conf.contains("cancel2order_ratio")
                                       ? symbol_conf["cancel2order_ratio"].get<int>()
                                       : std::numeric_limits<int>::max() - 1;
    reserve_map_[product.get()] = exe_config.reserve();
    controller_->InitExecutor(
        symbol,
        start_time_,
        exe_config,
        driver_->order()->GetUniqueSystem(MarketExchangeApi::FromString(exe_config.mea())),
        driver_,
        std::nullopt,
        lot_size_quote,
        qty_cancel_pct,
        is_randomised_post,
        cancel2order_ratio);
    if (arbitrageur_)
      arbitrageur_->InitExecutor(
          symbol,
          start_time_,
          exe_config,
          driver_->order()->GetUniqueSystem(MarketExchangeApi::FromString(exe_config.mea())),
          driver_,
          qty_cancel_pct,
          is_randomised_post,
          cancel2order_ratio);
  }
  timer_map_ = controller_->GetTimerMap();
  if (!timer_map_.empty()) {
    for (auto& [type, symbol] : timer_map_) {
      if (type == TimerType::CONTROLLER) {
        con_timers_[symbol] = std::make_unique<DeadlineTimer>();
        controller_->PassConTimer(symbol, std::move(con_timers_[symbol]));
      } else if (type == TimerType::FEED) {
        feed_timer_ = std::make_unique<DeadlineTimer>();
        feed_timer_->ExpiresEvery(config_["oc_config"]["feed_timer_sec"].get<double>() * 1e9);
      } else {
        NOTREACHED() << type;
      }
    }
  }
  if (!dry_report_) {
    strat_reporter_ = std::make_unique<StrategyReporter2>(driver->strategy()->config());
    for (auto& [symbol, exe_config] : exe_configs_) {
      auto& request = driver_->order()
                          ->GetUniqueSystem(MarketExchangeApi::FromString(exe_config.mea()))
                          ->GetAccountRequest();

      products_.push_back(CreateProductFromUniqueString(symbol, start_time_));
      if (config_["oc_config"]["configs"][symbol].contains("pos_offset")) {
        auto offset_config = new PassiveExecutorConfig();
        offset_config->set_lot_size(0);
        offset_config->set_max_pos(
            exe_config.reserve() +
            2 * config_["oc_config"]["configs"][symbol]["pos_offset"].get<double>());
        offset_config->set_min_pos(
            config_["oc_config"]["configs"][symbol]["pos_offset"].get<double>());
        offset_config->set_reserve(
            exe_config.reserve() +
            config_["oc_config"]["configs"][symbol]["pos_offset"].get<double>());
        strat_reporter_->RegisterExecutorConfig(
            start_time_,
            request,
            *CreateProductFromUniqueString(symbol, start_time_),
            *offset_config);
      } else {
        strat_reporter_->RegisterExecutorConfig(
            start_time_,
            request,
            *CreateProductFromUniqueString(symbol, start_time_),
            exe_config);
      }
    }
  }
}

void VmmStrategy::UpdateSimConfig(SimConfig* config) {
  for (const auto& p : reserve_map_) {
    config->add_reserve_symbols(p.first->absolute_norm());
    config->add_reserve_positions(p.second);
    config->set_start_timestamp(start_time_);
  }
}

void VmmStrategy::onBookFeed(const FeedUpdate& upd) {
  const auto& exch = upd.product().exchange_name();
  CHECK(latency_.find(exch) != latency_.end());
  const auto& latency = latency_[exch];
  const auto& ft = upd.timestamp();
  const auto& et = upd.ts().exchange_publish_timestamp();
  const auto& symbol = GetSymbol(upd.product());
  UpdateTimestamp(ft);

  if (ft - et > latency * 1'000'000LL && et != 0) {
    SPDLOG_ERROR(
        "[BOOK FEED ET FT DIFF GREATER THAN {}ms!!!!!] [{}] ft - et : {}ms ft : {}  et : {}",
        latency,
        symbol,
        (ft - et) / 1'000'000LL,
        ft,
        et);
    return;
  }

  std::unordered_map<std::string, VmmPriceResult> prev_results = controller_->GetPrevResult();
  pricer_->onBookFeed(upd, timestamp_, prev_results);
  const std::unordered_map<std::string, VmmPriceResult>& result = pricer_->GetResult();
  if (!result.empty()) {
    if (!update_orders_in_progress_) {
      update_orders_in_progress_ = true;
      controller_->onPricerUpdate(result, timestamp_);
      update_orders_in_progress_ = false;
    }
  }
}

void VmmStrategy::onTradeFeed(const FeedUpdate& upd) {
  UpdateTimestamp(upd.timestamp());

  pricer_->onTradeFeed(upd, timestamp_);
  // controller_->onTradeFeed();
}

void VmmStrategy::onHeartbeatFeed(const FeedTimer& timer) {
  for (auto& order_system : driver_->order()->GetAllSystems()) {
    IFeedSubscriber* fsubog = dynamic_cast<IFeedSubscriber*>(order_system->gateway());
    if (fsubog) fsubog->onHeartbeatFeed(timer);
  }

  UpdateTimestamp(timer.Timestamp());
  TimerType type = timer_map_[timer.TimerId() - 1].first;
  std::string symbol = timer_map_[timer.TimerId() - 1].second;
  if (type == TimerType::CONTROLLER) {
    controller_->onHeartbeatFeed(timer);
  } else if (type == TimerType::FEED) {
    const std::unordered_map<std::string, VmmPriceResult>& prev_result = pricer_->GetPrevResult();
    if (!update_orders_in_progress_) {
      update_orders_in_progress_ = true;
      bool arbi = false;
      if (arbitrageur_) arbi = arbitrageur_->onPricerUpdate(prev_result, timestamp_);
      if (!arbi && timestamp_ - last_filled_ts_ >
                       timer_freeze_ms_ * 1e6)  // Ignore Timer within tfm ms after any filled.
        controller_->onPricerUpdate(prev_result, timestamp_);
      update_orders_in_progress_ = false;
    }
  } else {
    NOTREACHED() << type;
  }
}

void VmmStrategy::onAccountInfo(const OrderUpdate& upd) {
  UpdateTimestamp(upd.timestamp());
  controller_->onAccountInfo(upd);
  if (strat_reporter_) {
    strat_reporter_->onAccountInfo(upd);
    for (const auto& product : products_) {
      double midp = pricer_->GetMidp(product.get());
      if (midp == 0.0) continue;
      strat_reporter_->UpdateMidp(*product.get(), midp);
    }
  }

  constexpr int64_t interval = 60e9;
  if (timestamp_ - last_print_ts_ > interval) {
    for (auto& [symbol, exe_config] : exe_configs_) {
      SPDLOG_INFO(
          "{} {}",
          symbol,
          driver_->order()
              ->GetUniqueSystem(MarketExchangeApi::FromString(exe_config.mea()))
              ->gateway()
              ->order_manager()
              .DebugString());
      last_print_ts_ = timestamp_;
    }
  }
}

void VmmStrategy::onAccountOrder(const OrderUpdate& upd) {
  UpdateTimestamp(upd.timestamp());
  if (!upd.is_product_order_info()) return;
  if (upd.product_order_info().event().type() == coin::proto::OrderEvent::ORDER_FILLED)
    last_filled_ts_ = timestamp_;
  controller_->onAccountOrder(upd);
  pricer_->onAccountOrder(upd);
  std::unordered_map<std::string, VmmPriceResult> result = pricer_->GetResult();
  if (!result.empty()) {
    if (!update_orders_in_progress_) {
      update_orders_in_progress_ = true;
      controller_->onPricerUpdate(result, timestamp_);
      update_orders_in_progress_ = false;
    }
  }
}

void VmmStrategy::onOrderLog(const OrderGatewayLog& order_log) {
  if (strat_reporter_) {
    strat_reporter_->GetMutableStrategyLogger()->WriteOrderLog(
        order_log,
        true,
        GetCurrentTimestamp());

    if (!opt_configs_.empty() && order_log.type() == coin::proto::OrderGatewayLog::BALANCE &&
        GetCurrentTimestamp() - last_opt_report_ts_ > 60 * 60 * 1e9) {
      for (const auto& [symbol, opt_config] : opt_configs_) {
        std::vector<std::string> tmp;
        boost::split(tmp, symbol, boost::is_any_of(":"));  // Spot:Binance:MASK-USDT
        const std::string& market_type = tmp[0];
        const std::string& exchange = tmp[1];
        const std::string& rel_symbol = tmp[2];
        for (const auto& [_, contract_config] : opt_config) {
          coin::proto::OrderGatewayLog options_pos_log;
          options_pos_log.set_type(coin::proto::OrderGatewayLog::POSITION);
          auto mutable_acct_req = options_pos_log.mutable_account_request();
          mutable_acct_req->set_market_type("Options");
          mutable_acct_req->set_exchange("Otc");
          mutable_acct_req->set_owner(opt_owners_[symbol]);
          auto mutable_position = options_pos_log.mutable_position();
          mutable_position->set_market_type("Options");
          mutable_position->set_exchange("Otc");
          auto mutable_each_position = mutable_position->add_each_position();
          // Add Option Parameters
          std::string option_type;
          double option_num;
          if (contract_config.find("call_num") != contract_config.end()) {
            option_type = "C";
            option_num = contract_config["call_num"].get<double>();
          } else {
            NOTREACHED() << "Other Options are Not Implemented";
          }
          const auto& volatility =
              config_["oc_config"]["configs"][symbol]["volatility"].get<double>();
          const auto& risk_free_rate =
              config_["oc_config"]["configs"][symbol]["risk_free_rate"].get<double>();
          const auto& option_symbol = GetOptionSymbol(contract_config, rel_symbol, option_type);
          coin::proto::ProductRequestProto underlying_product_request;
          underlying_product_request.set_market_type(market_type);
          underlying_product_request.set_exchange(exchange);
          underlying_product_request.set_symbol(rel_symbol);
          mutable_each_position->set_symbol(option_symbol);
          mutable_each_position->set_net_position(option_num);
          mutable_each_position->set_volatility(volatility);
          mutable_each_position->set_risk_free_rate(risk_free_rate);
          mutable_each_position->mutable_underlying_product()->CopyFrom(underlying_product_request);
          strat_reporter_->GetMutableStrategyLogger()->WriteOrderLog(
              options_pos_log,
              true,
              GetCurrentTimestamp());
        }
      }
      last_opt_report_ts_ = GetCurrentTimestamp();
    }
  }
}

void VmmStrategy::onCleanupAux(Driver* driver, int num_retries) {
  controller_->onCleanupAux(driver, num_retries);
}

void VmmStrategy::onCleanup(Driver* driver) {
  driver->SetImmediateExitOnSignal(false);
  driver->PostTask([this, driver]() { this->onCleanupAux(driver, 0); });
}

}  // namespace coin2::strategy::vmm
